/**
 *
 */
package com.honeybees.framework.mybatis;

import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.ibatis.exceptions.TooManyResultsException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.RowBounds;
import org.mybatis.spring.MyBatisSystemException;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.honeybees.framework.exception.DataAccessException;
import com.honeybees.framework.spring.mvc.model.ModelSupport;

/**
 * <b>MyBatis DAO层父类</b>
 *
 * <p>
 * &#64;Autowire<br/>
 * private MyBatisDaoSupport dao;
 * </p>
 *
 * @author 李远明
 * @version 2015-12-05 22:35:02，2016-09-20 09:34 MyBatis查询的Debug重点：
 *          <p>
 *          <b> insert、delete、update： </b> <br>
 *          {@link org.apache.ibatis.executor.SimpleExecutor#doUpdate(MappedStatement, Object)} <br>
 *          {@link org.apache.ibatis.executor.statement.PreparedStatementHandler#update(java.sql.Statement)}
 *          <p>
 *          <b> select： </b> <br>
 *          <br>
 *          {@link org.apache.ibatis.executor.BaseExecutor#query(MappedStatement, Object, RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, BoundSql)}
 *          <br>
 *          {@link org.apache.ibatis.executor.SimpleExecutor#doQuery(MappedStatement, Object, RowBounds, org.apache.ibatis.session.ResultHandler, BoundSql)}
 *          <br>
 *          {@link org.apache.ibatis.executor.statement.PreparedStatementHandler#query(java.sql.Statement, org.apache.ibatis.session.ResultHandler)}
 *          <br>
 *          {@link org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets(java.sql.Statement)}
 *          <br>
 *          {@link org.apache.ibatis.executor.resultset.ResultSetWrapper}
 *          <p>
 *          <b> 操作日志： </b> <br>
 *          {@link org.apache.ibatis.logging.jdbc.ResultSetLogger} <br>
 * @see SqlSessionTemplate.SqlSessionInterceptor#invoke(Object, java.lang.reflect.Method, Object[])
 * @since 2014-10-16 14:11:45
 */
//@Component("mybatisDao")
public final class MyBatisDaoSupport {

    /**
     * 日志
     */
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 数据库管理
     */
    //@Resource
    private SqlSessionTemplate sessionTemplate;

    /**
     * 点
     *
     * @version 2016-03-31 15:49:27 用于分隔MyBatis mapper文件中名称空间与“SQL语句id”的点
     * <p>
     * private static final String NS_DOT = "User" + MyBatisDaoSupport.DOT;
     */
    public static final char DOT = '.';

    public MyBatisDaoSupport() {
    }

    /**
     * <dl>
     * <dt><b> 执行插入语句 </b></dt>
     * <p>
     * <dd>INSERT INTO ... VALUES ...</dd>
     * </dl>
     *
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.insert
     *                    <p>
     *                    &lt;insert id="insert" parameterType="user"&gt;<br/>
     *                    INSERT INTO ..(...)<br/>
     *                    VALUES(...)<br/>
     *                    &lt;/insert&gt;<br/>
     *                    </p>
     * @param parameter   参数
     * @return 数据库中数据更改的条数
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2014-10-10 22:21:27
     */
    public final int insert(String statementId, Object parameter) throws DataAccessException {
        return this.sessionTemplate.insert(statementId, parameter);
    }

    /**
     * <dl>
     * <dt><b> 执行删除语句 </b></dt>
     * <p>
     * <dd>DELETE FROM ... WHERE ...</dd>
     * </dl>
     *
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.delete
     *                    <p>
     *                    &lt;delete id="delete" parameterType="user"&gt;<br/>
     *                    DELETE FROM ..<br/>
     *                    &lt;trim prefix="WHERE" prefixOverrides="AND | OR"&gt;<br/>
     *                    &lt;if test="id != null and id != ''"&gt;AND id=#{id}&lt;/if&gt;<br/>
     *                    ...<br/>
     *                    &lt;/trim><br/>
     *                    &lt;/delete&gt;<br/>
     * @param parameter   参数
     * @return 数据库中数据更改的条数
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2014-10-10 22:27:16
     */
    public final int delete(String statementId, Object parameter) throws DataAccessException {
        return this.sessionTemplate.delete(statementId, parameter);
    }

    /**
     * <dl>
     * <dt><b> 执行更新语句 </b></dt>
     * <p>
     * <dd>UPDATE ... SET ..., ... WHERE ...</dd>
     * </dl>
     *
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.update
     *                    <p>
     *                    &lt;update id="update" parameterType="user"&gt;<br/>
     *                    UPDATE ..<br/>
     *                    &lt;trim prefix="SET" suffixOverrides=","&gt;<br/>
     *                    &lt;if test="id != null and id != ''"&gt;id=#{id},&lt;/if&gt;<br/>
     *                    ...<br/>
     *                    &lt;/trim&gt;<br/>
     *                    WHERE id=#{id}<br/>
     *                    &lt;/update&gt;<br/>
     *                    </p>
     * @param parameter   参数
     * @return 数据库中数据更改的条数
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2014-10-10 22:31:23
     */
    public final int update(String statementId, Object parameter) throws DataAccessException {
        return this.sessionTemplate.update(statementId, parameter);
    }

    /**
     * <dl>
     * <dt><b> 查询所有满足条件的数据 </b></dt>
     * <p>
     * <dd>SELECT FROM ... WHERE ...</dd>
     * </dl>
     *
     * @param <T>         返回对象的类型
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.select
     *                    <p>
     *                    &lt;select id="select" parameterType="user" resultMap="user"&gt;<br/>
     *                    SELECT ..<br/>
     *                    FROM ..<br/>
     *                    &lt;trim prefix="WHERE" prefixOverrides="AND | OR"&gt;<br/>
     *                    &lt;if test="id != null and id != ''"&gt;AND id=#{id}&lt;/if&gt;<br/>
     *                    ...<br/>
     *                    &lt;/trim&gt;<br/>
     *                    ORDER BY id ASC<br/>
     *                    &lt;/select&gt;<br/>
     *                    </p>
     * @param parameter   参数
     * @return 满足条件的数据
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2014-10-10 20:16:42
     */
    public final <T> List<T> selectList(String statementId, Object parameter) throws DataAccessException {
        return this.sessionTemplate.selectList(statementId, parameter);
    }

    /**
     * <dl>
     * <dt><b> 执行分页查询 </b></dt>
     * <p>
     * <dd>调用前，请先使用
     * {@link com.honeybees.framework.spring.mvc.WebController#setPageParameter(HttpServletRequest, ModelSupport)}
     * 方法设置分页信息，查询完成后，使用
     * {@link com.honeybees.framework.spring.mvc.WebController#initPage(List, ModelSupport)}方法设置页面数据，最后，向页面返回
     * {@link com.honeybees.framework.pagination.Page} 对象。</dd>
     * <p>
     * <dd><b> 如果分页变量不存在，或为默认（offset=0，limit=0），则直接查询所有数据。 </b></dd>
     * </p>
     * </dl>
     *
     * @param <T>         返回对象的类型
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.select
     *                    <p>
     *                    &lt;select id="select" parameterType="user" resultMap="user"&gt;<br/>
     *                    SELECT ..<br/>
     *                    FROM ..<br/>
     *                    &lt;trim prefix="WHERE" prefixOverrides="AND | OR"&gt;<br/>
     *                    &lt;if test="id != null and id != ''"&gt;AND id=#{id}&lt;/if&gt;<br/>
     *                    ...<br/>
     *                    &lt;/trim&gt;<br/>
     *                    ORDER BY id ASC<br/>
     *                    &lt;/select&gt;<br/>
     *                    </p>
     * @param parameter   参数
     * @return 满足条件的数据
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2014-09-24 22:30:22
     * @see #selectList(String, Object)
     * @see com.honeybees.framework.spring.mvc.WebController#setPageParameter(HttpServletRequest,
     * ModelSupport)
     * @see com.honeybees.framework.spring.mvc.WebController#initPage(List, ModelSupport)
     * @see com.honeybees.framework.mybatis.interceptor.PaginationInterceptor#intercept(org.apache.ibatis.plugin.Invocation)
     */
    public final <T> List<T> selectPage(String statementId, Object parameter) throws DataAccessException {

        ModelSupport model;
        int offset; // FIXME 从哪一条数据开始查询（数据库中）
        int limit; // FIXME 每页显示的数据条数
        if (parameter instanceof ModelSupport) {
            model = (ModelSupport) parameter;
            offset = model.getOffset();
            limit = model.getLimit();
            // this.logger.debug("selectPage offset: [{}], limit: [{}]", offset, limit);
        } else { // 抛异常 2015-10-26 23:15
            throw new IllegalArgumentException("所有实体类必须继承模型基类：" + ModelSupport.class.getName());
        }

        if ((offset == 0) && (limit == 0)) { // 无分页信息，查找所有满足条件的记录
            this.logger.debug("do selectPage...false");
            List<T> rowsList = this.sessionTemplate.selectList(statementId, parameter);
            int rowsCount = rowsList.size();
            model.setTotalCount(rowsCount);
            return rowsList;
        } else { // 有分页信息，设置 RowBounds 信息，通过 PaginationInterceptor 拦截，实现分页
            this.logger.debug("do selectPage...true");
            RowBounds rowBounds = new RowBounds(offset, limit);
            List<T> pageList = this.sessionTemplate.selectList(statementId, parameter, rowBounds);
            return pageList;
        }
    }

    /**
     * <dl>
     * <dt><b> 查询单个对象 </b></dt>
     * <p>
     * <dd>SELECT FROM ... WHERE ...</dd>
     * <dd>根据条件，查询单个对象；当查询结果多于一个时，抛出异常。请重新输入更精确的查询条件。</dd>
     * </dl>
     *
     * @param <T>         返回对象的类型
     * @param statementId SQL语句id，推荐使用命名空间区分，如 User.select
     *                    <p>
     *                    &lt;select id="select" parameterType="user" resultMap="user"&gt;<br/>
     *                    SELECT ..<br/>
     *                    FROM ..<br/>
     *                    &lt;trim prefix="WHERE" prefixOverrides="AND | OR"&gt;<br/>
     *                    &lt;if test="id != null and id != ''"&gt;AND id=#{id}&lt;/if&gt;<br/>
     *                    ...<br/>
     *                    &lt;/trim&gt;<br/>
     *                    ORDER BY id ASC<br/>
     *                    &lt;/select&gt;<br/>
     *                    </p>
     * @param parameter   参数
     * @return 查询结果
     * @throws DataAccessException 可能发生的异常
     * @author 李远明
     * @version 2015-11-04 11:28:17
     */
    public final <T> T selectOne(String statementId, Object parameter) throws DataAccessException {
        try {
            T object = this.sessionTemplate.selectOne(statementId, parameter);
            return object;
        } catch (MyBatisSystemException e) {
            Throwable cause = e.getCause();
            // this.logger.error("selectOne error", e);
            this.logger.error("selectOne error: " + cause.getClass().getSimpleName());
            if (cause instanceof TooManyResultsException) {
                throw new DataAccessException("想要查询一个对象，但结果多于一项，请提供更精确的查询条件。");
            } else {
                throw new DataAccessException(e.getMessage());
            }
        } catch (Exception e) {
            throw new DataAccessException(e.getMessage());
        }
    }

    /**
     * 在Spring配置文件中定义MyBatisDaoSupport，不用数据库时，可以不引入 applicationContext-dataSource.xml 文件
     *
     * @param sessionTemplate org.mybatis.spring.SqlSessionTemplate
     * @since 2017-04-15 10:19:47
     */
    public void setSessionTemplate(SqlSessionTemplate sessionTemplate) {
        this.sessionTemplate = sessionTemplate;
    }
}
